SPDY 即“Speedy”发音,有速度之意,从名字便可知谷歌当年提出的这个方案的目标就是为了解决 HTTP 的性能瓶颈,缩短 web 页面的 load 时间。SPDY 是一个二进制协议,并不像 HTTP 那样直观易读。
SPDY 的设计
SPDY 解决的问题
- 多路复用:这名字听起来很高大上。其实理解起来,就是在一个 TCP 连接中,分出了多个 SPDY Stream 流通道,每个通道是并行的,不用相互等待,且各自负责一个请求和响应。这无疑会降低延迟和提升带宽的使用率。之前 HTTP/1.1 就面临着 head-of-line blocking 队首阻塞问题,也就是一个 response 响应如果迟迟没有回来,会阻塞住后续的 response 响应。后面我们用 WWDC 的一些截图来说明问题。
- 请求优先级:SPDY 允许为通道中的请求设置优先级,使得高优先级的请求能够优先得到回复响应。
- 请求头部压缩:我们知道,HTTP 协议是无状态的,无法对之前的请求和响应状态进行管理,而 Cookie 的引入正是为了解决这个问题。服务端通过客户端发送的 Cookie 来知晓是哪个客户端发送的 HTTP 请求,从而进行后续的操作。在 HTTP 请求的头部,还有一个 User Agent,这个字段携带着客户端的一些信息,如 iOS/Android 系统版本等信息。显而易见的是,Cookie 以及 User Agent 在每次的请求中会被携带进去,会耗费一定的流量。SPDY能够对 HTTP 请求头部进行压缩,会一定程度上节省不少流量。
- 服务器推送:HTTP 请求一般由客户端主动发送,服务端进行响应。而 SPDY 可以让服务端将客户端需要的内容 push 过去,不需要被动的等待客户端发送请求。有点像我们目前的 socket 连接下的服务器推送。
关于多路复用
我们使用【WWDC2014 707 Session whats_new_in_foundation_networking】中的几张图来说明下问题:
图1: HTTP/1.1 未使用 pipelining
style.css
和data.xml
的请求必须在收到image.jpg
的 response 之后才能依次发送出去,可想而知,这种方式会造成高延迟和低带宽使用率。图2: HTTP/1.1 使用 pipelining
style.css
和data.xml
的请求不必等到image.jpg
响应回来之后才进行发送,而是在image.jpg
请求发出去之后就依次发送了出去,而style.css
和data.xml
的响应(图中深色部分)必须要在image.jpg
的响应之后。如果image.jpg
请求的响应数据量比较大(图片较大),加之网络延迟严重等因素,会造成style.css
和data.xml
的响应迟迟不能回来,而对于网页渲染而言,资源的优先级却是data.xml
为最高优先级,style.css
资源为次优先级,image.jpg
为最低,最高优先级的资源却被最低优先级的资源请求影响,也就是受到了image.jpg
响应的阻塞,即head-of-line blocking
队首阻塞。图3: SPDY 多路复用
我们从图中看到,SPDY的多路复用,从请求的发送时序上和
HTTP/1.1
使用pipelining
是一样的,关键不同的地方在于响应返回时序上。在这个 TCP 连接中,分出了3个SPDY Stream
流通道,即image
,style.css
,data.xml
3个资源对应的流通道,每个通道各自负责一个请求和响应,并行且不用相互等待。
我该使用 SPDY 么?
- 更好的用户体验
- 对于长连接的延迟的减少可以明显改善用户体验。
- 苹果发现:在某些情况下,SPDY速度要比 HTTP/1.1 快25%。
- 额外的获益
- 减少 CPU 的使用率(一次SSL握手过程取代多次)
- 相同的服务器架构支持更多的客户端连接
SPDY 并不是总优于 HTTP/1.1:
- HTTP/1.1使用多个并行TCP连接要比SPDY的单个TCP连接要快。
- 许多SPDY的实现因为 CRIME 漏洞的影响禁用了头部压缩,所以头部压缩并不能带来很大的收益。
iOS8启用SPDY
苹果于 iOS8 和 OSX Yosemite 后支持了SPDY,NSURLSession
默认就支持了SPDY,支持的版本范围有 SPDY/2,SPDY/3,SPDY/3.1。
1 | NSURL *url = [NSURL URLWithString:@"https://www.example.com/"]; NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){ }]; [task resume]; |
将会默认启用SPDY,原来的代码不用做任何的改变。